iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0

https://ithelp.ithome.com.tw/upload/images/20231005/20141551Sjj27UGeS5.jpg

前言

前面透過建立靜態端點實作了自己的 RSS Feed 算是一個簡單的練習起步,這次更進一步透過建立整個集合的資料來完成「集合文章搜尋功能」。

定義問題

隨著文章愈來愈多會需要一個查詢文章的功能,透過用戶輸入文章相關的訊息,比對文章資訊(標題、文章短描述)並顯示出最相關的文章。透過以上描述統整出會需要以下幾個要點:

  1. 用戶可以輸入查詢的介面
  2. 獲取當前集合資料的方式
  3. 可以模糊比對輸入內容與當前所有資料的系統
  4. 顯示查詢結果的介面

實作

統整下來,以上的問題會需要較為頻繁的操縱到 DOM 且有一定的複雜程度,因此會引入框架來協助處理資料與畫面更新的問題,避免糾結在一些底層的 DOM 操作上面,這裡我使用 Preact (類似輕量化的 React),可以選擇自己習慣的框架即可。

第一步:製作輸入查詢的介面

創建一個 jsx 元件,製作一個 <input>,只在用戶第一次 Focus 的時候抓取要比對的資料,並且切換 isLoading 的狀態。

function getPosts() {
  setIsLoading(true);
  return fetch('/search.json')
    .then((res) => {
      if (!res.ok) {
        throw new Error('Network response was not ok');
      }
      return res.json();
    })
    .catch((err) => console.error(err))
    .finally(setIsLoading(false));
}

async function handleInputFocus() {
    setIsSearching(true);
    if (!posts.length) {
    setPosts(await getPosts());
  }
}

並且在 Blur 的時候,如果目標是在搜尋元件之外就將 isSearching 的狀態切換為否。

function handleInputBlur(e) {
    const { currentTarget } = e;

    // 給瀏覽器時間去 Focus 下一個元素
    requestAnimationFrame(() => {
      if (!currentTarget.contains(document.activeElement)) {
        setIsSearching(false);
      }
    });
  }

由於在手機尺寸並不會顯示這個區塊的功能,因此可以設定只有在裝置尺寸 1024px 以上才載入相關的客戶端 JavaScript ,達成「有用到才載入」,節省不必要的浪費。

<Search client:media="(min-width: 1024px)" />

第二步:製作資料端點與索取資料

由於前面 getPosts 會期望網站的根部有 search.json 這樣一個檔案,因此需要製作一個端點並產生一個要搜尋資料的 JSON 檔,這裡以我的部落格為範例,內容包含了文章的:連結、標題、繁中標題、短介紹、主題色。

// search.json.ts
import { getCollection } from 'astro:content';

async function getPosts() {
  const avaliablePosts = await getCollection('post', (post) => (import.meta.env.PROD ? !post.data.isDraft : true));
  return avaliablePosts.map((post) => ({
    slug: post.slug,
    title: post.data.title,
    titleTC: post.data.titleTC,
    excerpt: post.data.excerpt,
    themeColor: post.data.themeColor,
  }));
}

export async function GET() {
  return new Response(JSON.stringify(await getPosts()), {
    status: 200,
    headers: {
      'content-type': 'application/json',
    },
  });
}

第三步:模糊搜尋比對結果

每當用戶輸入時如果 fuse 實例並沒有被創造過,就創造一次到 fuseInstance 當中,並且透過給 fuse 實例的搜尋方法傳入「目前查詢的詞彙」可以得到模糊比對出來最貼近用戶搜尋的文章。

let fuseInstance
function handleInputChange(e) {
  if (!fuseInstance) {
    fuseInstance = new Fuse(posts, {
      includeScore: true,
      shouldSort: true,
      threshold: 0.5,
      keys: [
        {
          name: 'titleTC',
          weight: 1,
        },
        {
          name: 'title',
          weight: 1,
        },
      ],
    });
  }

  setResults(fuseInstance.search(e.target.value));
}

第四步:將結果展示到畫面當中

基本上你只需要把 results 這筆資料展示到畫面上即可。

總結

由於我有使用到 PreactFuse.js,但還是希望這篇文章能夠盡量的著重在 Astro 本身,因此有些描述會省略掉,可以了解其中大概念即可。

延伸閱讀

(目前 iT 邦不支援 Astro 語法的高光 😅,因此歡迎到我的部落格閱讀本系列相關文章,正同步更新中!)


上一篇
Day19 - 實作 RSS 端點
下一篇
Day21 - 部屬升空
系列文
網頁開發沒有這麼簡單過!實際案例帶你上手 Astro.js30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言